#include "moof.h"
#include <string.h>

uint32_t pos_sequence_number = 0;
uint32_t pos_base_data_offset = 0;
uint32_t pos_base_media_decode_time = 0;

struct DataOffsetPos {
    bool data_offset_present;
    uint32_t offset;
};

enum BufError write_mfhd(struct BitBuf *ptr, const uint32_t sequence_number);
enum BufError write_traf(
    struct BitBuf *ptr, const uint32_t sequence_number,
    const uint64_t base_data_offset, const uint64_t base_media_decode_time,
    const uint32_t default_sample_duration,
    const struct SampleInfo *samples_info, const uint32_t samples_info_len,
    struct DataOffsetPos *data_offset);
enum BufError write_tfhd(
    struct BitBuf *ptr, const uint32_t sequence_number,
    const uint64_t base_data_offset, const uint64_t base_media_decode_time,
    const uint32_t default_sample_size, const uint32_t default_sample_duration,
    const struct SampleInfo *samples_info, const uint32_t samples_info_len,
    struct DataOffsetPos *data_offset);
enum BufError
write_tfdt(struct BitBuf *ptr, const uint64_t base_media_decode_time);
enum BufError write_trun(
    struct BitBuf *ptr, const struct SampleInfo *samples_info,
    const uint32_t samples_info_count, struct DataOffsetPos *data_offset);

enum BufError
write_mdat(struct BitBuf *ptr, const char *data, const uint32_t len) {
    enum BufError err;
    uint32_t start_atom = ptr->offset;
    err = put_u32_be(ptr, 0);
    chk_err;
    err = put_str4(ptr, "mdat");
    chk_err;
    err = put_u32_be(ptr, len);
    chk_err;
    err = put(ptr, data, len);
    chk_err;
    err = put_u32_be_to_offset(ptr, start_atom, ptr->offset - start_atom);
    chk_err;
    return BUF_OK;
}

enum BufError write_moof(
    struct BitBuf *ptr, const uint32_t sequence_number,
    const uint64_t base_data_offset, const uint64_t base_media_decode_time,
    const uint32_t default_sample_duration,
    const struct SampleInfo *samples_info, const uint32_t samples_info_len) {
    enum BufError err;
    uint32_t start_atom = ptr->offset;
    err = put_u32_be(ptr, 0);
    chk_err;
    err = put_str4(ptr, "moof");
    chk_err;
    err = write_mfhd(ptr, sequence_number);
    chk_err;

    struct DataOffsetPos data_offset;
    data_offset.offset = 0;
    err = write_traf(
        ptr, sequence_number, base_data_offset, base_media_decode_time,
        default_sample_duration, samples_info, samples_info_len, &data_offset);
    chk_err;
    if (data_offset.data_offset_present)
        err = put_u32_be_to_offset(
            ptr, data_offset.offset,
            ptr->offset + 4 /*mdat size*/ + 4 /*mdat id*/);
    chk_err;

    err = put_u32_be_to_offset(ptr, start_atom, ptr->offset - start_atom);
    chk_err;
    return BUF_OK;
}

enum BufError write_mfhd(struct BitBuf *ptr, const uint32_t sequence_number) {
    enum BufError err;
    uint32_t start_atom = ptr->offset;
    err = put_u32_be(ptr, 0);
    chk_err;
    err = put_str4(ptr, "mfhd");
    chk_err err = put_u8(ptr, 0); // 1 version
    err = put_u8(ptr, 0);
    chk_err;
    err = put_u8(ptr, 0);
    chk_err;
    err = put_u8(ptr, 0);
    chk_err;
    // 3 flags
    pos_sequence_number = ptr->offset;
    err = put_u32_be(ptr, sequence_number);
    chk_err; // 4 sequence_number
    err = put_u32_be_to_offset(ptr, start_atom, ptr->offset - start_atom);
    chk_err;
    return BUF_OK;
}

enum BufError write_traf(
    struct BitBuf *ptr, const uint32_t sequence_number,
    const uint64_t base_data_offset, const uint64_t base_media_decode_time,
    const uint32_t default_sample_duration,
    const struct SampleInfo *samples_info, const uint32_t samples_info_len,
    struct DataOffsetPos *data_offset) {
    enum BufError err;
    uint32_t start_atom = ptr->offset;
    err = put_u32_be(ptr, 0);
    chk_err;
    err = put_str4(ptr, "traf");
    chk_err;
    err = write_tfhd(
        ptr, sequence_number, base_data_offset, base_media_decode_time,
        samples_info[0].size, default_sample_duration, samples_info,
        samples_info_len, data_offset);
    chk_err;
    err = write_tfdt(ptr, base_media_decode_time);
    chk_err;
    err = write_trun(ptr, samples_info, samples_info_len, data_offset);
    chk_err;
    err = put_u32_be_to_offset(ptr, start_atom, ptr->offset - start_atom);
    chk_err;
    return BUF_OK;
}

enum BufError write_tfhd(
    struct BitBuf *ptr, const uint32_t sequence_number,
    const uint64_t base_data_offset, const uint64_t base_media_decode_time,
    const uint32_t default_sample_size, const uint32_t default_sample_duration,
    const struct SampleInfo *samples_info, const uint32_t samples_info_len,
    struct DataOffsetPos *data_offset) {
    enum BufError err;
    uint32_t start_atom = ptr->offset;
    err = put_u32_be(ptr, 0);
    chk_err;
    err = put_str4(ptr, "tfhd");
    chk_err;

    err = put_u8(ptr, 0);
    chk_err; // 1 byte version
    uint64_t flags = 0x0;
    const bool base_data_offset_present = false;
    const bool sample_description_index_present = false;
    const bool default_sample_duration_present = true;
    const bool default_sample_size_present = true;
    const bool default_sample_flags_present = true;
    const bool duration_is_empty = false;
    const bool default_base_is_moof = false;

    if (base_data_offset_present) {
        flags = flags | 0x000001;
    } // base-data-offset-present
    if (sample_description_index_present) {
        flags = flags | 0x000002;
    } // sample-description-index-present
    if (default_sample_duration_present) {
        flags = flags | 0x000008;
    } // default-sample-duration-present
    if (default_sample_size_present) {
        flags = flags | 0x000010;
    } // default-sample-size-present
    if (default_sample_flags_present) {
        flags = flags | 0x000020;
    } // default-sample-flags-present
    if (duration_is_empty) {
        flags = flags | 0x010000;
    } // duration-is-empty
    if (default_base_is_moof) {
        flags = flags | 0x020000;
    } // default-base-is-moof
    err = put_u8(ptr, flags >> 16);
    chk_err;
    err = put_u8(ptr, flags >> 8);
    chk_err;
    err = put_u8(ptr, flags >> 0);
    chk_err; // 3 flags

    err = put_u32_be(ptr, 1);
    chk_err; // 4 track_ID
    if (base_data_offset_present) {
        pos_base_data_offset = ptr->offset;
        err = put_u64_be(ptr, base_data_offset);
        chk_err;
    }
    // if sample_description_index_present { buf.put_u32_be(0); } // 4
    // default_sample_description_index
    if (default_sample_duration_present) {
        err = put_u32_be(ptr, default_sample_duration);
        chk_err;
    }
    if (default_sample_size_present) {
        err = put_u32_be(ptr, default_sample_size);
        chk_err;
    }
    if (default_sample_flags_present) {
        err = put_u32_be(ptr, 16842752);
        chk_err;
    }

    err = put_u32_be_to_offset(ptr, start_atom, ptr->offset - start_atom);
    chk_err;
    return BUF_OK;
}

enum BufError
write_tfdt(struct BitBuf *ptr, const uint64_t base_media_decode_time) {
    enum BufError err;
    uint32_t start_atom = ptr->offset;
    err = put_u32_be(ptr, 0);
    chk_err;
    err = put_str4(ptr, "tfdt");
    chk_err err = put_u8(ptr, 1); // 1 version
    err = put_u8(ptr, 0);
    chk_err;
    err = put_u8(ptr, 0);
    chk_err;
    err = put_u8(ptr, 0);
    chk_err; // 3 flags
    pos_base_media_decode_time = ptr->offset;
    err = put_u64_be(ptr, base_media_decode_time);
    chk_err; // 4 baseMediaDecodeTime
    err = put_u32_be_to_offset(ptr, start_atom, ptr->offset - start_atom);
    chk_err;
    return BUF_OK;
}

enum BufError write_trun(
    struct BitBuf *ptr, const struct SampleInfo *samples_info,
    const uint32_t samples_info_count, struct DataOffsetPos *data_offset) {
    enum BufError err;
    uint32_t start_atom = ptr->offset;
    err = put_u32_be(ptr, 0);
    chk_err;
    err = put_str4(ptr, "trun");
    chk_err;

    err = put_u8(ptr, 0);
    chk_err; // 1 version
    const bool data_offset_present = true;
    const bool first_sample_flags_present = false;
    const bool sample_duration_present = true;
    const bool sample_size_present = true;
    const bool sample_flags_present = true;
    const bool sample_composition_time_offsets_present = true;
    {
        uint64_t flags = 0x0;
        if (data_offset_present) {
            flags = flags | 0x000001;
        } // 0x000001 data-offset-present.
        if (first_sample_flags_present) {
            flags = flags | 0x000004;
        } // 0x000004 first-sample-flags-present
        if (sample_duration_present) {
            flags = flags | 0x000100;
        } // 0x000100 sample-duration-present
        if (sample_size_present) {
            flags = flags | 0x000200;
        } // 0x000200 sample-size-present
        if (sample_flags_present) {
            flags = flags | 0x000400;
        } // 0x000400 sample-flags-present
        if (sample_composition_time_offsets_present) {
            flags = flags | 0x000800;
        } // 0x000800 sample-composition-time-offsets-present
        err = put_u8(ptr, flags >> 16);
        chk_err;
        err = put_u8(ptr, flags >> 8);
        chk_err;
        err = put_u8(ptr, flags >> 0);
        chk_err // 3 flags
    }
    err = put_u32_be(ptr, samples_info_count);
    chk_err; // 4 sample_count

    data_offset->data_offset_present = data_offset_present;
    data_offset->offset =
        ptr->offset; // save pointer to this place. we will change size after
                     // moof atom will created
    if (data_offset_present) {
        err = put_i32_be(ptr, 0);
        chk_err;
    } // 4 fake data_offset

    if (first_sample_flags_present) {
        err = put_u32_be(ptr, 33554432);
        chk_err;
    } // 4 first_sample_flags
    for (uint32_t i = 0; i < samples_info_count; ++i) {
        const struct SampleInfo sample_info = samples_info[i];
        if (sample_duration_present) {
            err = put_u32_be(ptr, sample_info.duration);
            chk_err;
        } // 4 sample_duration
        if (sample_size_present) {
            err = put_u32_be(ptr, sample_info.size);
            chk_err;
        } // 4 sample_size
        if (sample_flags_present) {
            err = put_u32_be(ptr, sample_info.flags);
            chk_err;
        } // 4 sample_flags
        if (sample_composition_time_offsets_present) {
            err = put_i32_be(ptr, sample_info.composition_offset);
            chk_err;
        }
    }

    err = put_u32_be_to_offset(ptr, start_atom, ptr->offset - start_atom);
    chk_err;
    return BUF_OK;
}
